import decodeGoogleEarthEnterpriseData from '../Core/decodeGoogleEarthEnterpriseData.js';
import GoogleEarthEnterpriseTileInformation from '../Core/GoogleEarthEnterpriseTileInformation.js';
import RuntimeError from '../Core/RuntimeError.js';
import pako from '../ThirdParty/pako_inflate.js';
import createTaskProcessorWorker from './createTaskProcessorWorker.js';

    // Datatype sizes
    var sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
    var sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
    var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;

    var Types = {
        METADATA : 0,
        TERRAIN : 1,
        DBROOT : 2
    };

    Types.fromString = function(s) {
        if (s === 'Metadata') {
            return Types.METADATA;
        } else if (s === 'Terrain') {
            return Types.TERRAIN;
        } else if (s === 'DbRoot') {
            return Types.DBROOT;
        }
    };

    function decodeGoogleEarthEnterprisePacket(parameters, transferableObjects) {
        var type = Types.fromString(parameters.type);
        var buffer = parameters.buffer;
        decodeGoogleEarthEnterpriseData(parameters.key, buffer);

        var uncompressedTerrain = uncompressPacket(buffer);
        buffer = uncompressedTerrain.buffer;
        var length = uncompressedTerrain.length;

        switch (type) {
            case Types.METADATA:
                return processMetadata(buffer, length, parameters.quadKey);
            case Types.TERRAIN:
                return processTerrain(buffer, length, transferableObjects);
            case Types.DBROOT:
                transferableObjects.push(buffer);
                return {
                    buffer : buffer
                };
        }

    }

    var qtMagic = 32301;

    function processMetadata(buffer, totalSize, quadKey) {
        var dv = new DataView(buffer);
        var offset = 0;
        var magic = dv.getUint32(offset, true);
        offset += sizeOfUint32;
        if (magic !== qtMagic) {
            throw new RuntimeError('Invalid magic');
        }

        var dataTypeId = dv.getUint32(offset, true);
        offset += sizeOfUint32;
        if (dataTypeId !== 1) {
            throw new RuntimeError('Invalid data type. Must be 1 for QuadTreePacket');
        }

        // Tile format version
        var quadVersion = dv.getUint32(offset, true);
        offset += sizeOfUint32;
        if (quadVersion !== 2) {
            throw new RuntimeError('Invalid QuadTreePacket version. Only version 2 is supported.');
        }

        var numInstances = dv.getInt32(offset, true);
        offset += sizeOfInt32;

        var dataInstanceSize = dv.getInt32(offset, true);
        offset += sizeOfInt32;
        if (dataInstanceSize !== 32) {
            throw new RuntimeError('Invalid instance size.');
        }

        var dataBufferOffset = dv.getInt32(offset, true);
        offset += sizeOfInt32;

        var dataBufferSize = dv.getInt32(offset, true);
        offset += sizeOfInt32;

        var metaBufferSize = dv.getInt32(offset, true);
        offset += sizeOfInt32;

        // Offset from beginning of packet (instances + current offset)
        if (dataBufferOffset !== (numInstances * dataInstanceSize + offset)) {
            throw new RuntimeError('Invalid dataBufferOffset');
        }

        // Verify the packets is all there header + instances + dataBuffer + metaBuffer
        if (dataBufferOffset + dataBufferSize + metaBufferSize !== totalSize) {
            throw new RuntimeError('Invalid packet offsets');
        }

        // Read all the instances
        var instances = [];
        for (var i = 0; i < numInstances; ++i) {
            var bitfield = dv.getUint8(offset);
            ++offset;

            ++offset; // 2 byte align

            var cnodeVersion = dv.getUint16(offset, true);
            offset += sizeOfUint16;

            var imageVersion = dv.getUint16(offset, true);
            offset += sizeOfUint16;

            var terrainVersion = dv.getUint16(offset, true);
            offset += sizeOfUint16;

            // Number of channels stored in the dataBuffer
            offset += sizeOfUint16;

            offset += sizeOfUint16; // 4 byte align

            // Channel type offset into dataBuffer
            offset += sizeOfInt32;

            // Channel version offset into dataBuffer
            offset += sizeOfInt32;

            offset += 8; // Ignore image neighbors for now

            // Data providers
            var imageProvider = dv.getUint8(offset++);
            var terrainProvider = dv.getUint8(offset++);
            offset += sizeOfUint16; // 4 byte align

            instances.push(new GoogleEarthEnterpriseTileInformation(bitfield, cnodeVersion,
                imageVersion, terrainVersion, imageProvider, terrainProvider));
        }

        var tileInfo = [];
        var index = 0;

        function populateTiles(parentKey, parent, level) {
            var isLeaf = false;
            if (level === 4) {
                if (parent.hasSubtree()) {
                    return; // We have a subtree, so just return
                }

                isLeaf = true; // No subtree, so set all children to null
            }
            for (var i = 0; i < 4; ++i) {
                var childKey = parentKey + i.toString();
                if (isLeaf) {
                    // No subtree so set all children to null
                    tileInfo[childKey] = null;
                } else if (level < 4) {
                    // We are still in the middle of the subtree, so add child
                    //  only if their bits are set, otherwise set child to null.
                    if (!parent.hasChild(i)) {
                        tileInfo[childKey] = null;
                    } else {
                        if (index === numInstances) {
                            console.log('Incorrect number of instances');
                            return;
                        }

                        var instance = instances[index++];
                        tileInfo[childKey] = instance;
                        populateTiles(childKey, instance, level + 1);
                    }
                }
            }
        }

        var level = 0;
        var root = instances[index++];
        if (quadKey === '') {
            // Root tile has data at its root and one less level
            ++level;
        } else {
            tileInfo[quadKey] = root; // This will only contain the child bitmask
        }

        populateTiles(quadKey, root, level);

        return tileInfo;
    }

    function processTerrain(buffer, totalSize, transferableObjects) {
        var dv = new DataView(buffer);

        var offset = 0;
        var terrainTiles = [];
        while (offset < totalSize) {
            // Each tile is split into 4 parts
            var tileStart = offset;
            for (var quad = 0; quad < 4; ++quad) {
                var size = dv.getUint32(offset, true);
                offset += sizeOfUint32;
                offset += size;
            }
            var tile = buffer.slice(tileStart, offset);
            transferableObjects.push(tile);
            terrainTiles.push(tile);
        }

        return terrainTiles;
    }

    var compressedMagic = 0x7468dead;
    var compressedMagicSwap = 0xadde6874;

    function uncompressPacket(data) {
        // The layout of this decoded data is
        // Magic Uint32
        // Size Uint32
        // [GZipped chunk of Size bytes]

        // Pullout magic and verify we have the correct data
        var dv = new DataView(data);
        var offset = 0;
        var magic = dv.getUint32(offset, true);
        offset += sizeOfUint32;
        if (magic !== compressedMagic && magic !== compressedMagicSwap) {
            throw new RuntimeError('Invalid magic');
        }

        // Get the size of the compressed buffer - the endianness depends on which magic was used
        var size = dv.getUint32(offset, (magic === compressedMagic));
        offset += sizeOfUint32;

        var compressedPacket = new Uint8Array(data, offset);
        var uncompressedPacket = pako.inflate(compressedPacket);

        if (uncompressedPacket.length !== size) {
            throw new RuntimeError('Size of packet doesn\'t match header');
        }

        return uncompressedPacket;
    }
export default createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);
